home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Publishing / ImagePortfolio / Source / Portfolio.m < prev   
Text File  |  1994-04-01  |  28KB  |  998 lines

  1. // -------------------------------------------------------------------------------------
  2. // Portfolio.m - image Palette instance manager
  3. // Martin D. Flynn, NeXT Computer, Inc.
  4. // You may freely copy, distribute and reuse the code in this example.
  5. // NeXT disclaims any warranty of any kind, expressed or implied, as to its
  6. // fitness for any particular use.
  7. // -------------------------------------------------------------------------------------
  8.  
  9. #import <stdlib.h>
  10. #import <stdio.h>
  11. #import <string.h>
  12. #import <math.h>
  13. #import <libc.h>
  14. #import    <sys/types.h>
  15. #import    <sys/stat.h>
  16. #import    <sys/time.h>
  17. #import <defaults/defaults.h>
  18. #import <dpsclient/psops.h>
  19. #import <dpsclient/dpsNeXT.h>
  20. #import <objc/objc-runtime.h>
  21. #import <objc/NXBundle.h>
  22. #import <appkit/graphics.h>
  23. #import <appkit/Button.h>
  24. #import <appkit/Panel.h>
  25. #import <appkit/Matrix.h>
  26. #import <appkit/ScrollView.h>
  27. #import <appkit/NXCursor.h>
  28. #import <appkit/NXImage.h>
  29. #import <appkit/NXBitmapImageRep.h>
  30. #import <appkit/NXEPSImageRep.h>
  31. #import <appkit/SavePanel.h>
  32. #import <appkit/FontManager.h>
  33. #import "ExpandedView.h"
  34. #import "PaletteCell.h"
  35. #import "PaletteMatrix.h"
  36. #import "Portfolio.h"
  37. #import "ImagePortfolio.h"
  38.  
  39. // -------------------------------------------------------------------------------------
  40.  
  41. /* static image ids */
  42. static id                openFolder   = (id)nil;
  43. static id                closedFolder = (id)nil;
  44. static id                multiFile    = (id)nil;
  45. static id                stopIcon     = (id)nil;
  46.  
  47. /* list of instantiated windows */
  48. static id                instanceList = (id)nil;
  49.  
  50. /* main window location scale */
  51. static NXPoint            winPosScale  = { 0.20, 0.85 };
  52. static NXSize            winOffset = { 64.0, -24.0 };
  53.  
  54. // -------------------------------------------------------------------------------------
  55. // misc defines
  56. #define    isLOADING        ([iconButtonId altImage] == stopIcon)
  57. #define    dftCellROWS        1
  58. #define    dftCellCOLUMNS    5
  59. #define dftFormatNAME    "PortfolioFormat"
  60. #define    X                origin.x
  61. #define    Y                origin.y
  62. #define    W                size.width
  63. #define    H                size.height
  64.  
  65. // -------------------------------------------------------------------------------------
  66. @implementation Portfolio
  67.  
  68. // -------------------------------------------------------------------------------------
  69. // internal support
  70.  
  71. /* return first instance */
  72. + firstInstance
  73. {
  74.     return (instanceList && [instanceList count])? [instanceList objectAt:0]: (id)nil;
  75. }
  76.  
  77. // -------------------------------------------------------------------------------------
  78. // internal support
  79.  
  80. /* set icon button image */
  81. - _setIcon:imageId
  82. {
  83.     if (imageId) [iconButtonId setAltImage:imageId];
  84.     [iconButtonId setImage:[iconButtonId altImage]];
  85.     return self;
  86. }
  87.  
  88. /* set window title from file name */
  89. - _setWindowTitle:(char*)fileName
  90. {
  91.     char    buff[MAXPATHLEN + 1], *name;
  92.  
  93.     /* set last file path */
  94.     if (fileName) {
  95.         [NXApp setLastPathNoCopy:filePATH(fileName)];
  96.         name = fileName;
  97.     } else {
  98.         int len = strlen(strcpy(buff, [NXApp lastPath]));
  99.         strcat(buff, ((!len||(buff[len-1]!='/'))?"/UNTITLED":"UNTITLED"));
  100.         name = buff;
  101.     }
  102.   
  103.     /* change window title and add to windows menu */
  104.     [paletteWindow setTitleAsFilename:name];
  105.  
  106.     return self;
  107. }
  108.  
  109. /* make save panel */
  110. - _savePanel:(char*)title :(char*)extn
  111. {
  112.     id    savePanel = [SavePanel new];
  113.     [savePanel setTitle:title];
  114.     [savePanel setPrompt:"File:"];
  115.     [savePanel setRequiredFileType:extn];
  116.     [savePanel setDirectory:[NXApp lastPath]];
  117.     return savePanel;
  118. }
  119.  
  120. /* (re)adjust window size to fit cell size */
  121. - _adjustPreference:(char*)prefBuff
  122. {
  123.     int        rows, cols;
  124.     NXSize    winSize, *cellSize, *gap;
  125.     NXRect    fRect;
  126.     BOOL        rtn;
  127.   
  128.     /* free preference buffer and check for successful */
  129.     rtn = [iconMatrix setPreferences:prefBuff returnRows:&rows cols:&cols];
  130.     free(prefBuff);
  131.     if (!rtn) return self;
  132.   
  133.     /* calculate minimum window size */
  134.     cellSize = [iconMatrix cellSize];
  135.     gap = [iconMatrix intercell];
  136.     [self getWindowSize:&winSize forCellSize:cellSize gap:gap rows:rows cols:cols];
  137.     [paletteWindow getFrame:&fRect];
  138.     fRect.Y += fRect.H - winSize.height;
  139.     fRect.size = winSize;
  140.   
  141.     /* resize and re-display window */
  142.     [paletteWindow placeWindow:&fRect];
  143.     [self windowDidResize:paletteWindow];
  144.     [paletteWindow display];
  145.     [paletteWindow makeKeyAndOrderFront:(id)nil];
  146.  
  147.     return self;
  148. }
  149.  
  150. /* (load thread) recursive add directory to image list */
  151. - _addDirectory:(char*)dirPath :(BOOL)chkExtn
  152. {
  153.     struct stat    statBuf;
  154.     if (abortLoad) return (id)nil;
  155.     if (!dirPath || stat(dirPath, &statBuf)) return (id)nil;
  156.     if (statBuf.st_mode & S_IFDIR) sendDIRECTORY(dirPath, self, _cmd, (id)((int)chkExtn));
  157.     else {
  158.         if (chkExtn && !_validExtension(dirPath)) return (id)nil;
  159.         [iconMatrix addImage:dirPath];
  160.     }
  161.     return self;
  162. }
  163.  
  164. /* (load thread) load from doc file */
  165. - _loadDocument:(char*)fileName :(BOOL)doPref :(BOOL)checkExtension
  166. {
  167.     int        p;
  168.     char    name[MAXPATHLEN + 1], *ptr;
  169.     FILE    *fNum;
  170.   
  171.     /* return if no file name, or if file can't be openned */
  172.     if (!fileName || !(fNum=fopen(fileName,"r"))) return (id)nil;
  173.   
  174.     /* read image paths from file */
  175.     for (*name = 0; !abortLoad && fgets(name, sizeof(name) - 1, fNum);) {
  176.         if (name[(p = strlen(name) - 1)] == '\n') name[p] = 0;
  177.         if (*name != ':') { [self _addDirectory:name :checkExtension]; continue; }
  178.         if (doPref) {
  179.             ptr = NXCopyStringBuffer(name + 1);
  180.             [self mainThreadPerform:@selector(_adjustPreference:) with:(id)ptr wait:YES];
  181.         }
  182.     }
  183.     fclose(fNum);
  184.   
  185.     /* tell matrix to resize */
  186.     [iconMatrix mainThreadPerform:@selector(sizeToCells) wait:NO];
  187.   
  188.     return self;
  189. }
  190.  
  191. /* (load thread) load file list */
  192. - _doLoad:(fileLIST*)fileList
  193. {
  194.     char        *s, *d, filePath[MAXPATHLEN + 1];
  195.     fileLIST    *fl, *next;
  196.   
  197.     /* return if no list */
  198.     if (!fileList) return self;
  199.   
  200.     /* initial load lock */
  201.     for (fl = fileList; fl;) {
  202.  
  203.         /* continue loading if not stopped */
  204.         if (!abortLoad) {
  205.             BOOL quit, chkExt, openDoc, setEdit;
  206.     
  207.             /* load list of images (separated by tabs) */
  208.             chkExt  = (fl->flags & 2)?YES:NO;
  209.             openDoc = (fl->flags & 1)?YES:NO;
  210.             setEdit = (fl->flags & 1)?NO:YES;
  211.             for (quit = NO, s = fl->list; !abortLoad && !quit && *s;) {
  212.                 for (d = filePath; (*d = *s++) && (*d != '\t'); d++);    // copy til tab or nil
  213.                 if (!*d) quit = YES; else *d = 0;                        // check end-of-list
  214.                 if (strcmp(fileEXT(filePath)+1, docEXTENSION))
  215.                     [self _addDirectory:filePath :chkExt];
  216.                 else
  217.                     [self _loadDocument:filePath :openDoc :NO];
  218.             }
  219.  
  220.             /* set window edited icon (setEdit only true on the first fileLIST loaded) */
  221.             [paletteWindow mainThreadPerform:@selector(setDocEdited:)
  222.                       with:(id)((int)setEdit) wait:NO];
  223.  
  224.         }
  225.     
  226.         /* free list and move to next list of images */
  227.         mutex_lock(loadMutex);
  228.         next = fl->next;
  229.         free(fl->list);
  230.         free(fl);
  231.         fl = next;
  232.         if (!fl) loadList = (fileLIST*)nil;
  233.         mutex_unlock(loadMutex);
  234.     
  235.       }
  236.   
  237.     /* loading complete */
  238.     [self mainThreadPerform:@selector(_loadComplete) wait:NO];
  239.     return self;
  240.     
  241. }
  242.  
  243. /* invoked by load thread when loading is complete */
  244. - _loadComplete
  245. {
  246.     mutex_lock(loadMutex);
  247.     if (!loadList) {            // make sure another load hasn't been requested
  248.         abortLoad = YES;
  249.         [self _setIcon:closedFolder];
  250.         [iconMatrix resizeAndDisplay];
  251.     }
  252.     mutex_unlock(loadMutex);
  253.     return self;
  254. }
  255.  
  256. // -------------------------------------------------------------------------------------
  257. // create a new instance
  258.  
  259. /* general initialization */
  260. - init
  261. {
  262.     return [self initFromFile:(char*)nil];
  263. }
  264.  
  265. /* initialize and open file name */
  266. - initFromFile:(const char*)fileName
  267. {
  268.     return [self initFromFile:fileName registerWindow:YES];
  269. }
  270.  
  271. /* initialize and open file name */
  272. - initFromFile:(const char*)fileName registerWindow:(BOOL)regWindow
  273. {
  274.     id            docView;
  275.     int            rows, cols;
  276.     const char    *dftFmt;
  277.     NXSize        cellSize, scrnSize, gap, clipSize;
  278.     NXRect        docFrame, winRect;
  279.     static int    winCount = 0;
  280.  
  281.     /* initialize static vars */
  282.     if (!openFolder  ) openFolder   = [NXImage findImageNamed:"openFolder"  ];
  283.     if (!closedFolder) closedFolder = [NXImage findImageNamed:"closedFolder"];
  284.     if (!multiFile   ) multiFile    = [NXImage findImageNamed:"multiFile"   ];
  285.     if (!stopIcon    ) stopIcon     = [NXImage findImageNamed:"stop"        ];
  286.     if (!instanceList) instanceList = listALLOC(1);
  287.  
  288.     /* init super and add myself to the list of active instances */
  289.     [super init];
  290.     [instanceList addObject:self];
  291.     [self resignActivePortfolio:self];
  292.  
  293.     /* load nib */
  294.     [NXApp loadNibSection:"Portfolio.nib" owner:self];
  295.  
  296.     /* set icon button to send message on mouse down */
  297.     fileIcon = (id)nil;
  298.     [iconButtonId sendActionOn:NX_MOUSEDOWNMASK];
  299.     [self _setIcon:closedFolder];
  300.   
  301.     /* file name load list flags */
  302.     abortLoad = YES;
  303.     loadMutex = mutex_alloc();
  304.     loadList  = (fileLIST*)nil;
  305.  
  306.     /* make a new matrix */
  307.     docView = [paletteScroll docView];
  308.     [docView getFrame:&docFrame];
  309.     [docView getCellSize:&cellSize];
  310.     iconMatrix = [[PaletteMatrix alloc] initFrame:&docFrame];
  311.     [iconMatrix setCellSize:&cellSize];
  312.     [iconMatrix setFont:[[docView cellAt:0:0] font]];
  313.     [iconMatrix setDelegate:self];
  314.     [iconMatrix setTarget:self];
  315.     [iconMatrix setDoubleAction:@selector(showLargeImage:)];
  316.     [paletteScroll setDocView:iconMatrix];
  317.     [paletteScroll setPageScroll:0.0];                // full page scrolling
  318.     [[paletteScroll setHorizScroller:(id)nil] free];    // remove the horiz scroller
  319.     [docView free];                                    // free the old view
  320.  
  321.     /* defaults */
  322.     dftFmt = NXReadDefault([NXApp appName], dftFormatNAME);
  323.     if (![iconMatrix setPreferences:(char*)dftFmt returnRows:&rows cols:&cols]) {
  324.         rows = dftCellROWS;
  325.         cols = dftCellCOLUMNS;
  326.     }
  327.  
  328.     /* calculate new window size and location */
  329.     cellSize = *[iconMatrix cellSize];            // re-read cellSize
  330.     gap = *[iconMatrix intercell];
  331.     [NXApp getScreenSize:&scrnSize];
  332.     [self getWindowSize:&winRect.size forCellSize:&cellSize gap:&gap rows:rows cols:cols];
  333.     winRect.X = floor(scrnSize.width *winPosScale.x)+((float)winCount*winOffset.width );
  334.     winRect.Y = floor(scrnSize.height*winPosScale.y)+((float)winCount*winOffset.height);
  335.     if (winRect.X > (scrnSize.width  - 64.0)) winRect.X = scrnSize.width  - 64.0;
  336.     if (winRect.Y > (scrnSize.height - 24.0)) winRect.Y = scrnSize.height - 24.0;
  337.     winRect.Y -= winRect.H;
  338.     winCount = (++winCount) % 5;
  339.  
  340.     /* reposition window */
  341.     [paletteWindow placeWindow:&winRect];
  342.     [self windowDidResize:paletteWindow];
  343.     [paletteScroll getContentSize:&clipSize];
  344.     windowOverhead.width  = winRect.W - clipSize.width ;
  345.     windowOverhead.height = winRect.H - clipSize.height;
  346.   
  347.     /* clear window header statistics */
  348.     [self cellResignedSelected:(id)nil];
  349.  
  350.     /* register window */
  351.     isRegistered = NO;
  352.     allowDrop = regWindow;
  353.     [self _registerWindow];
  354.     [paletteWindow setDelegate:self];
  355.     [paletteWindow setMiniwindowIcon:"doc"];
  356.     [paletteWindow addToEventMask:NX_MOUSEENTEREDMASK];
  357.   
  358.     /* calculate absolute minimum window size */
  359.     minCellSize.width  = 70.0;
  360.     minCellSize.height = 40.0;
  361.     [self getWindowSize:&minWindowSize forCellSize:&minCellSize gap:&gap rows:1 cols:1];
  362.   
  363.     /* load file if specified */
  364.       sourceFile = (char*)nil;
  365.     if (fileName) {
  366.         BOOL isDoc=(index(fileName,'\t')||strcmp(fileEXT(fileName),dotDocEXTENSION))?NO:YES;
  367.         if (isDoc) sourceFile = NXCopyStringBuffer(fileName);
  368.         [self loadFileList:fileName :isDoc:!isDoc]; 
  369.     }
  370.     [self _setWindowTitle:sourceFile];
  371.  
  372.     /* make window key */
  373.     [paletteWindow display];
  374.     [paletteWindow makeKeyAndOrderFront:(id)nil];
  375.   
  376.     /* initialize expanded view */
  377.     [largeView setScrollView:largeScroller];
  378.     [largePanel setDelegate:largeView];
  379.  
  380.     return self;
  381. }
  382.  
  383. /* free image Palette window manager */
  384. - free
  385. {
  386.     [paletteWindow free];    // hopefully this takes care of the whole thing
  387.     [largePanel free];
  388.     if (fileIcon) [fileIcon free];
  389.     mutex_free(loadMutex);
  390.     free(sourceFile);
  391.     return self;
  392. }
  393.  
  394. // -------------------------------------------------------------------------------------
  395. // active portfolio delegate
  396.  
  397. /* become active portfolio */
  398. - becomeActivePortfolio:sender
  399. {
  400.     isActivePortfolio = YES;
  401.     [paletteWindow becomeMainWindow];
  402.     return self;
  403. }
  404.  
  405. /* resign active portfolio */
  406. - resignActivePortfolio:sender
  407. {
  408.     isActivePortfolio = NO;
  409.     return self;
  410. }
  411.  
  412. /* return true if active */
  413. - (BOOL)isActivePortfolio
  414. {
  415.     return isActivePortfolio;
  416. }
  417.   
  418. /* make portfolio active */
  419. + makeActivePortfolio:(Portfolio*)portObj
  420. {
  421.     [instanceList makeObjectsPerform:@selector(resignActivePortfolio:) with:(id)nil];
  422.     [portObj becomeActivePortfolio:(id)nil];
  423.     return self;
  424. }
  425.  
  426. /* return active portfolio */
  427. + activePortfolio
  428. {
  429.     id    pObj = (id)nil;
  430.     int    i = [instanceList count];
  431.     while(i) if ([(pObj = [instanceList objectAt:--i]) isActivePortfolio]) break;
  432.     return pObj;
  433. }
  434.  
  435. // -------------------------------------------------------------------------------------
  436. // document status
  437.  
  438. /* return true if there are any unsaved files */
  439. + (BOOL)isDocEdited
  440. {
  441.     int    i = [instanceList count];
  442.     while(i) if ([[instanceList objectAt:--i] isDocEdited]) return YES;
  443.     return NO;
  444. }
  445.  
  446. /* return true if this document has been edited */
  447. - (BOOL)isDocEdited
  448. {
  449.     return [paletteWindow isDocEdited];
  450. }
  451.  
  452. /* return true if loading new images (main thread only!) */
  453. - (BOOL)isLoading
  454. {
  455.     return isLOADING;
  456. }
  457.  
  458. // -------------------------------------------------------------------------------------
  459. // font
  460.  
  461. /* return matrix font */
  462. - font
  463. {
  464.     return [iconMatrix font];
  465. }
  466.  
  467. /* change font */
  468. - setFont:fontObj
  469. {
  470.     [iconMatrix setFont:fontObj];
  471.     return self;
  472. }
  473.  
  474. // -------------------------------------------------------------------------------------
  475. // button methods
  476.  
  477. /* show Palette window */
  478. - show:sender
  479. {
  480.     [paletteWindow display];
  481.     [paletteWindow makeKeyAndOrderFront:(id)nil];
  482.     return self;
  483. }
  484.  
  485. /* open file */
  486. - open:sender
  487. {
  488.     return self;
  489. }
  490.   
  491. /* save file names (called by first responder) */
  492. - save:sender
  493. {
  494.   
  495.     /* get file name to save */
  496.     if (!sourceFile) {
  497.         const char *temp;
  498.         id pSave = [self _savePanel:"Save Image Palette" :docEXTENSION];
  499.         if (![pSave runModalForDirectory:[NXApp lastPath] file:""]) return self;
  500.         if (!(temp=[pSave filename])) return self;
  501.         sourceFile = NXCopyStringBuffer(temp);
  502.     }
  503.   
  504.     /* open , write data, and close */
  505.     [iconMatrix saveToFile:sourceFile];
  506.     [self _setWindowTitle:sourceFile];
  507.  
  508.     return self;
  509. }
  510.  
  511. /* save file names (called by first responder) */
  512. - saveAs:sender
  513. {
  514.     if (sourceFile) { free(sourceFile); sourceFile = (char*)nil; }
  515.     return [self save:sender];
  516. }
  517.  
  518. /* save defaults */
  519. - saveDefaults:sender
  520. {
  521.     char    buff[512];
  522.     NXWriteDefault([NXApp appName], dftFormatNAME, [iconMatrix getPreferenceString:buff]);
  523.     return self;
  524. }
  525.  
  526. // -------------------------------------------------------------------------------------
  527. // calculate window size overhead
  528. // - This method needs to know the layout of the screen to properly calculate its size
  529.  
  530. - getWindowSize:(NXSize*)windowSize forCellSize:(NXSize*)cellSize gap:(NXSize*)gapSize
  531.         rows:(int)rows cols:(int)cols
  532. {
  533.     NXRect    fRect, cRect;
  534.     NXSize    actualSize;
  535.  
  536.     /* calculate actual size of content area */
  537.     actualSize.width  = (cellSize->width  + gapSize->width ) * cols;
  538.     actualSize.height = (cellSize->height + gapSize->height) * rows;
  539.  
  540.     /* calc size of scrollView */
  541.     [[paletteScroll class] getFrameSize:&cRect.size forContentSize:&actualSize
  542.         horizScroller:([paletteScroll horizScroller]?YES:NO)
  543.         vertScroller:([paletteScroll vertScroller]?YES:NO)
  544.         borderType:[paletteScroll borderType]];
  545.  
  546.     /* calc size of window contentView */
  547.     [windowHeader getFrame:&fRect];
  548.       cRect.H += fRect.H;
  549.   
  550.     /* calc window size */
  551.     cRect.X = cRect.Y = 0.0;
  552.     [[paletteWindow class] getFrameRect:&fRect forContentRect:&cRect
  553.           style:[paletteWindow style]];
  554.     *windowSize = fRect.size;
  555.  
  556.     return self;
  557. }
  558.  
  559. /* get current displayed row/col count */
  560. - getDisplayedRows:(int*)rows cols:(int*)cols
  561. {
  562.     NXSize    scSize, *cellSize = [iconMatrix cellSize], *gap = [iconMatrix intercell];
  563.     [paletteScroll getContentSize:&scSize];
  564.     *rows = (int)rint(scSize.height / (cellSize->height + gap->height));
  565.     *cols = (int)rint(scSize.width  / (cellSize->width  + gap->width ));
  566.     return self;
  567. }
  568.  
  569. /* return current cell size */
  570. - (NXSize*)cellSize
  571. {
  572.     return [iconMatrix cellSize];
  573. }
  574.  
  575. // -------------------------------------------------------------------------------------
  576. // show selected cell enlarged image (double action from matrix)
  577.  
  578. /* matrix doubleAction */
  579. - showLargeImage:sender
  580. {
  581.     id        imageCell = [iconMatrix selectedCell];
  582.   
  583.     /* hide large panel and return if no selected cell */
  584.     [largePanel orderOut:self];
  585.     if (!imageCell) return self;
  586.   
  587.     /* show enlarged image */
  588.     [self showImage:[imageCell image] title:(char*)[imageCell cellTitle]];
  589.   
  590.     return self;
  591. }
  592.  
  593. /* show specified image */
  594. - showImage:imageId title:(char*)title
  595. {
  596.     BOOL            hScroll, vScroll;
  597.     NXRect            cRect, fRect;
  598.     static NXSize    maxSize, scrSize = { 0.0, 0.0 };
  599.  
  600.     /* init max screen size */
  601.     if (!scrSize.width) {
  602.         [NXApp getScreenSize:&scrSize];
  603.         maxSize.width  = floor(scrSize.width  * 0.80);
  604.         maxSize.height = floor(scrSize.height * 0.80);
  605.     }
  606.  
  607.     /* hide large panel and return if no image */
  608.     [largePanel orderOut:self];
  609.     if (!imageId) return self;
  610.  
  611.     /* set panel image */
  612.     [largeView setImage:imageId];
  613.     [largePanel setTitle:title];
  614.   
  615.     /* resize panel */
  616.     hScroll = vScroll = NO;
  617.     [largeView getFrame:&cRect];
  618.     [Window getFrameRect:&fRect forContentRect:&cRect style:[largePanel style]];
  619.     if ((fRect.W >= scrSize.width) || (fRect.H >= scrSize.height)) {
  620.         fRect.size = maxSize;
  621.         [largeView windowWillResize:largePanel toSize:&fRect.size];
  622.         [Window getContentRect:&cRect forFrameRect:&fRect style:[largePanel style]];
  623.         hScroll = vScroll = YES;
  624.     }
  625.     [largeScroller setHorizScrollerRequired:hScroll];
  626.     [largeScroller setVertScrollerRequired:vScroll];
  627.     cRect.X = cRect.Y = 0.0;
  628.     [largeScroller setFrame:&cRect];
  629.     [largePanel sizeWindow:cRect.W :cRect.H];
  630.   
  631.     /* show enlarged image */
  632.     [largePanel center];
  633.     [largePanel display];
  634.     [largePanel makeKeyAndOrderFront:self];
  635.   
  636.     return self;
  637. }
  638.  
  639. // -------------------------------------------------------------------------------------
  640. // pass file list to workspace manager
  641.  
  642. /* send files to workspace manager for dragging */
  643. - iconDrag:sender
  644. {
  645.     NXRect    bFrame, rect;
  646.     char    *fullPath;
  647.   
  648.     /* ignore if not called by proper button */
  649.     if (sender != iconButtonId) return self;                        // ignore any imposters
  650.   
  651.     /* check for STOP button */
  652.     if (isLOADING) { abortLoad = YES; return self; }                // stop button pressed
  653.   
  654.     /* ignore if no files selected */
  655.     if (!(fullPath = [iconMatrix selectedCellPaths])) return self;    // ignore if no files
  656.   
  657.     /* drag selected files */
  658.     [iconButtonId getBounds:&bFrame];
  659.     rect.W = 48.0;
  660.     rect.H = 48.0;
  661.     rect.X = (bFrame.W - rect.W) / 2.0;
  662.     rect.Y = (bFrame.H - rect.H) / 2.0;
  663.     [self _unregisterWindow];                                        // unregister myself
  664.     [iconButtonId dragFile:fullPath fromRect:&rect slideBack:YES event:[NXApp currentEvent]];
  665.     [self _registerWindow];                                            // re-register myself
  666.   
  667.     /* free file list */
  668.     free(fullPath);
  669.   
  670.     return self;
  671. }
  672.  
  673. // -------------------------------------------------------------------------------------
  674. // PaletteMatrix delegate methods
  675.  
  676. /* (main thread) load tabbed file list */
  677. - loadFileList:(const char*)fileNames :(BOOL)openDoc :(BOOL)chkExtn
  678. {
  679.     fileLIST    *fl;
  680.   
  681.     /* return if no files to load */
  682.     if (!fileNames) return self;
  683.   
  684.     /* allocate and fill file list structure */
  685.     fl = (fileLIST*)malloc(sizeof(fileLIST));
  686.     fl->list  = NXCopyStringBuffer(fileNames);
  687.     fl->flags = (chkExtn?2:0) | (openDoc?1:0);
  688.     fl->next  = (fileLIST*)nil;
  689.   
  690.     /* load images (fork new thread if necessary) */
  691.     mutex_lock(loadMutex);
  692.     if (loadList) loadList->next = fl;    // load already in progress
  693.     else {
  694.         abortLoad = NO;                        // reset abort flag
  695.         [self _setIcon:stopIcon];            // setup stop button
  696.         [iconMatrix clearSelectedCell];        // deselect any selected cells
  697.         [self forkPerform:@selector(_doLoad:) with:(id)fl detach:YES];
  698.     }
  699.     loadList = fl;
  700.     mutex_unlock(loadMutex);
  701.   
  702.     return self;
  703. }
  704.  
  705. /* set image statistics (only called when a cell is (de)selected) */
  706. - clearImageStats:iconImage        // 'closedFolder' or 'multiFile' only
  707. {
  708.     [iconPathId setStringValue:""];
  709.     [iconSizeId setStringValue:""];
  710.     [self _setIcon:iconImage];
  711.     return self;
  712. }
  713.  
  714. /* set image statistics */
  715. - setImageStats:imageCell
  716. {
  717.     NXRect    bBox;
  718.     char    buff[256];
  719.   
  720.     /* clear stats if no cell */
  721.     if (!imageCell) { [self clearImageStats:closedFolder]; return self; }
  722.   
  723.     /* fill info */
  724.     [iconPathId setStringValueNoCopy:[imageCell imagePath]];
  725.     [[imageCell image] getSize:&bBox.size];
  726.     sprintf(buff, "%.0f x %.0f", bBox.W, bBox.H);
  727.     [iconSizeId setStringValue:buff];
  728.   
  729.     /* set icon representation */
  730.     if (fileIcon) [fileIcon free];
  731.     fileIcon = [[Application workspace] getIconForFile:(char*)[imageCell imagePath]];
  732.     [self _setIcon:fileIcon];
  733.  
  734.     return self;
  735. }
  736.  
  737. /* image became selected */
  738. - cellBecameSelected:imageCell
  739. {
  740.     [largePanel orderOut:self];    // hide large panel
  741.     if ([iconMatrix selectedCellCount] <= 1) [self setImageStats:imageCell];
  742.     else { [largeView setImage:(id)nil]; [self clearImageStats:multiFile]; }
  743.     return self;
  744. }
  745.  
  746. /* image became selected */
  747. - cellResignedSelected:imageCell
  748. {
  749.     if ([iconMatrix selectedCellCount] > 1) [self clearImageStats:multiFile];
  750.     else [self setImageStats:[iconMatrix selectedCell]];
  751.     return self;
  752. }
  753.   
  754. // -------------------------------------------------------------------------------------
  755. // Workspace manager icon dragging support
  756. #define    isSHIFT(S)    ((([NXApp currentEvent]->flags) & (S))? YES : NO)
  757. static char            *wsTiffName = (char*)nil;
  758.  
  759. /* keyboard shifts states are now current, do load */
  760. - _delayLoad:sender
  761. {
  762.     [self loadFileList:wsTiffName:NO:(isSHIFT(NX_CONTROLMASK)?NO:YES)];
  763.     return self;
  764. }
  765.  
  766. /* indicate we accept dragging */
  767. - (BOOL)prepareForDragOperation:sender
  768. {
  769.     if (!wsTiffName) return NO;
  770.     return YES;
  771. }
  772.  
  773. /* files dropped */
  774. - (BOOL)performDragOperation:sender
  775. {
  776.     [self _setIcon:(id)nil];
  777.     if (!wsTiffName) return NO;
  778.     [NXApp activateSelf:YES];
  779.     [paletteWindow makeKeyAndOrderFront:(id)nil];
  780.     [self perform:@selector(_delayLoad:) with:self afterDelay:10 cancelPrevious:NO];
  781.     return YES;
  782. }
  783.  
  784. /* file icon exited */
  785. // - reset the open file folder icon
  786. - draggingExited:sender
  787. {
  788.     [self _setIcon:(id)nil];
  789.     return self;
  790. }
  791.  
  792. /* file icon entered */
  793. // - save the list of file names and the representing icon image
  794. - (NXDragOperation)draggingEntered:sender
  795. {
  796.     char        *fileList;
  797.     int            fileListLen;
  798.     Pasteboard    *pb = [sender draggingPasteboard];
  799.     if (wsTiffName) { free(wsTiffName); wsTiffName = (char*)nil; }
  800.     if ([pb readType:NXFilenamePboardType data:&fileList length:&fileListLen]) {
  801.         wsTiffName = NXCopyStringBuffer(fileList);
  802.         return NX_DragOperationCopy;
  803.     }
  804.     return NX_DragOperationNone;
  805. }
  806.  
  807. /* file icon moved to location */
  808. - (NXDragOperation)draggingUpdated:sender
  809. {
  810.     if (!wsTiffName) return NX_DragOperationNone;
  811.     [iconButtonId setImage:openFolder];
  812.     return NX_DragOperationCopy;
  813. }
  814.  
  815. /* register window with workspace manager */
  816. - _registerWindow
  817. {
  818.     NXAtom    pbsTypes[1];
  819.     if (!allowDrop || isRegistered) return self;
  820.     pbsTypes[0] = NXFilenamePboardType;
  821.     [paletteWindow registerForDraggedTypes:pbsTypes count:1];
  822.     isRegistered = YES;
  823.     return self;
  824. }
  825.  
  826. /* unregister window with workspace manager */
  827. - _unregisterWindow
  828. {
  829.     if (!allowDrop || !isRegistered) return self;
  830.     [paletteWindow unregisterDraggedTypes];
  831.     isRegistered = NO;
  832.     return self;
  833. }
  834.  
  835. // -------------------------------------------------------------------------------------
  836. // window delegate methods
  837.  
  838. /* window is resizing */
  839. - windowWillResize:windowId toSize:(NXSize*)newSize
  840. {
  841.     float        rows, cols, mRow, mCol;
  842.     NXSize        size, minSize, oneCellSize;
  843.     NXSize        *cellSize = [iconMatrix cellSize], *gap = [iconMatrix intercell];
  844.   
  845.     /* actual cell size */
  846.     oneCellSize = *cellSize;
  847.     oneCellSize.width  += gap->width ;
  848.     oneCellSize.height += gap->height;
  849.  
  850.     /* absolute minimum size */
  851.     if (newSize->width  < minWindowSize.width ) newSize->width  = minWindowSize.width ;
  852.     if (newSize->height < minWindowSize.height) newSize->height = minWindowSize.height;
  853.  
  854.     /* allow any window size if Alternate is pressed */
  855.     if (isSHIFT(NX_ALTERNATEMASK)) {
  856.         if (isLOADING) {    // retain original size
  857.             NXRect wFrame;
  858.             [windowId getFrame:&wFrame];
  859.             *newSize = wFrame.size;
  860.             return self;
  861.         }
  862.         [paletteScroll getContentSize:&size];
  863.         cols = size.width  / oneCellSize.width ;
  864.         rows = size.height / oneCellSize.height;
  865.         minSize.width  = rint((minCellSize.width +gap->width )*cols)+windowOverhead.width ;
  866.         minSize.height = rint((minCellSize.height+gap->height)*rows)+windowOverhead.height;
  867.         if (newSize->width  < minSize.width ) newSize->width  = minSize.width;
  868.         if (newSize->height < minSize.height) newSize->height = minSize.height;
  869.         return self;
  870.     }
  871.   
  872.     /* window size must hold at least one cell */
  873.     minSize.width  = windowOverhead.width  + oneCellSize.width ;
  874.     minSize.height = windowOverhead.height + oneCellSize.height;
  875.     if (newSize->width  < minSize.width ) newSize->width  = minSize.width ;
  876.     if (newSize->height < minSize.height) newSize->height = minSize.height;
  877.  
  878.     /* size window to cell boundary */
  879.     cols = rint((newSize->width  - windowOverhead.width ) / oneCellSize.width );
  880.     rows = rint((newSize->height - windowOverhead.height) / oneCellSize.height);
  881.     mCol = ceil((minWindowSize.width  - windowOverhead.width ) / oneCellSize.width );
  882.     mRow = ceil((minWindowSize.height - windowOverhead.height) / oneCellSize.height);
  883.     if (cols < mCol) cols = mCol;
  884.     if (rows < mRow) cols = mRow;
  885.     newSize->width  = windowOverhead.width  + cols * oneCellSize.width ;
  886.     newSize->height = windowOverhead.height + rows * oneCellSize.height;
  887.  
  888.     return self;
  889. }
  890.  
  891. /* window is resizing */
  892. - windowDidResize:windowId
  893. {
  894.     float        oldRows, oldCols;
  895.     NXRect    sFrame, matFrame, winFrame;
  896.     NXSize    scSize, *cellSize = [iconMatrix cellSize], *gap = [iconMatrix intercell];
  897.  
  898.     /* ignore if loading is in progress */
  899.     if (isSHIFT(NX_ALTERNATEMASK) && isLOADING) return self;
  900.  
  901.     /* get new window size */
  902.     [[windowId contentView] getFrame:&winFrame];
  903.  
  904.     /* reposition header */
  905.     [windowHeader getFrame:&sFrame];
  906.     winFrame.H -= sFrame.H;
  907.     sFrame.Y = winFrame.H;
  908.     sFrame.W = winFrame.W;
  909.     [windowHeader setFrame:&sFrame];
  910.     headerSize = sFrame.size;
  911.   
  912.     /* resize iconPathId text field */
  913.     [iconPathId getFrame:&sFrame];
  914.     sFrame.W = headerSize.width - sFrame.X;
  915.     [iconPathId setFrame:&sFrame];
  916.  
  917.     /* save current number of displayed rows/cols */
  918.     [paletteScroll getContentSize:&scSize];
  919.     oldCols = scSize.width  / (cellSize->width  + gap->width );
  920.     oldRows = scSize.height / (cellSize->height + gap->height);
  921.   
  922.     /* resize scroller */
  923.     [paletteScroll getFrame:&sFrame];
  924.     sFrame.size = winFrame.size;
  925.     sFrame.Y = 0.0;
  926.     sFrame.X = 0.0;
  927.     [paletteScroll setFrame:&sFrame];
  928.   
  929.     /* resize matrix within scroller */
  930.     [paletteScroll getContentSize:&scSize];
  931.     [iconMatrix getFrame:&matFrame];
  932.     matFrame.size = scSize;
  933.     [iconMatrix setFrame:&matFrame];
  934.  
  935.     /* if alternate key is pressed, then resize cells */
  936.     if (isSHIFT(NX_ALTERNATEMASK)) {
  937.         NXSize    size;
  938.         size.width  = rint(matFrame.W / oldCols) - gap->width;
  939.         size.height = rint(matFrame.H / oldRows) - gap->height;
  940.         [iconMatrix setCellSize:&size];
  941.     }
  942.   
  943.     /* size matrix view to fit cells */
  944.     [iconMatrix sizeToCells];
  945.   
  946.     return self;
  947. }
  948.  
  949. /* window will close */
  950. - windowWillClose:windowId
  951. {
  952.   
  953.     /* delay window close if still loading */
  954.     abortLoad = YES;
  955.     if (isLOADING) {
  956.         [windowId perform:@selector(performClose:) with:self
  957.             afterDelay:250 cancelPrevious:NO];
  958.         return (id)nil;
  959.     }
  960.   
  961.     /* check for saved */
  962.     if ([windowId isDocEdited]) {
  963.         int  rtn;
  964.         const char *save, *no, *cncl, *savf;
  965.           save = NXLocalizedString("Save", (char*)nil,(char*)nil);
  966.           no   = NXLocalizedString("No", (char*)nil,(char*)nil);
  967.           cncl = NXLocalizedString("Cancel", (char*)nil,(char*)nil);
  968.         if (sourceFile) {
  969.             char *name = fileNAME(sourceFile);
  970.             savf = NXLocalizedString("Save changes to %s?", (char*)nil,(char*)nil);
  971.             rtn = NXRunAlertPanel(save, savf, save, no, cncl, name);
  972.             free(name);
  973.         } else {
  974.             savf = NXLocalizedString("Save changes to UNTITLED?", (char*)nil,(char*)nil);
  975.             rtn = NXRunAlertPanel(save, savf, save, no, cncl);
  976.         }
  977.         if (rtn == NX_ALERTOTHER) return (id)nil;                    // Cancel
  978.         if (rtn == NX_ALERTDEFAULT) [self save:(id)nil];            // Save (else No)
  979.     }
  980.  
  981.     /* unregister / remove / and free */
  982.     [self _unregisterWindow];
  983.     [instanceList removeObject:self];
  984.     return [NXApp delayedFree:self];
  985.   
  986. }
  987.  
  988. /* window became key */
  989. - windowDidBecomeKey:windowId
  990. {
  991.     [[self class] makeActivePortfolio:self];
  992.     [windowId makeFirstResponder:iconMatrix];
  993.     [[FontManager new] setSelFont:[iconMatrix font] isMultiple:NO];
  994.     return self;
  995. }
  996.  
  997. @end
  998.